<!--
 * @Description: 分页表格页封装
 * @Author: HuiSir<https://gitee.com/huisir001>
 * @Date: 2025-07-27 11:49:55
 * @LastEditTime: 2025-09-26 16:14:12
-->
<template>
  <!-- @ts-expect-error -->
  <fy-table
    v-model:cols="cols"
    v-model:toolsOptions="toolsOptions"
    v-model:rowBtnsOptions="rowBtnsOptions"
    :loading="loading"
    :data="tableData"
    :page="usingPaging"
    :curr="currPage"
    :total="total"
    height="700px"
    @searchSubmit="handleSearch"
    @searchReset="handleReset"
    @toolsRefresh="getList"
    @pageSizeChange="pageSizeChange"
    @pageCurrChange="pageCurrChange"
    @toolsAddDialogSubmit="toolsAddDialogSubmit"
    @toolsDelete="toolsDelete"
    @rowBtnsSubmit="rowBtnsSubmit"
    @rowBtnsDelete="rowBtnsDelete"
  >
    <!-- 搜索表单插槽 -->
    <template
      v-for="item in searchSlotOpts"
      :key="item.key"
      v-slot:[item.slot]="scope"
    >
      <slot :name="item.slot" v-bind="scope" />
    </template>
    <!-- 工具栏左侧插槽，这里也可以放搜索表单 -->
    <template v-if="!!$slots.toolsLeft" #toolsLeft="scope">
      <slot name="toolsLeft" v-bind="scope" />
    </template>
    <!-- 工具栏右侧插槽 -->
    <template v-if="!!$slots.toolsRight" #toolsRight="scope">
      <slot name="toolsRight" v-bind="scope" />
    </template>
    <!-- 表格列插槽 -->
    <template
      v-for="colItem in tableSlotCols"
      :key="colItem.slot"
      v-slot:[colItem.slot]="scope"
    >
      <slot :name="colItem.slot" v-bind="scope" />
    </template>
    <!-- 工具栏新增弹窗表单插槽 -->
    <template
      v-for="item in toolsAddDialogSlotOptions"
      :key="item.key"
      v-slot:[item.slot]="scope"
    >
      <slot :name="item.slot" v-bind="scope" />
    </template>
    <!-- 行按钮左侧插槽 -->
    <template #rowBtnsLeft="scope">
      <slot name="rowBtnsLeft" v-bind="scope" />
    </template>
    <!-- 行按钮右侧插槽 -->
    <template #rowBtnsRight="scope">
      <slot name="rowBtnsRight" v-bind="scope" />
    </template>
    <!-- 行按钮编辑弹窗表单插槽 -->
    <template
      v-for="item in rowBtnsEditDialogSlotOptions"
      :key="item.key"
      v-slot:[item.slot]="scope"
    >
      <slot :name="item.slot" v-bind="scope" />
    </template>
    <!-- 行按钮新增弹窗表单插槽 -->
    <template
      v-for="item in rowBtnsAddDialogSlotOptions"
      :key="item.key"
      v-slot:[item.slot]="scope"
    >
      <slot :name="item.slot" v-bind="scope" />
    </template>
    <!-- 行按钮详情弹窗插槽 -->
    <template
      v-for="item in rowBtnsDetailDialogSlotOptions"
      :key="item.key"
      v-slot:[item.slot]="scope"
    >
      <slot :name="item.slot" v-bind="scope" />
    </template>
  </fy-table>
</template>

<script lang="ts">
export default {
  name: "TablePage",
};
</script>

<script lang="ts" setup>
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ref, computed, PropType, Ref, watch } from "vue";
import { ICol, SubmitCallbacks, DeleteCallbacks, FyTableInstance } from "@/ui";
import { IPageRowBtnsOptions, IPageToolsOptions } from "./types";
import { ElMessage } from "element-plus";

const props = defineProps({
  /**
   * 列表API请求方法
   */
  listRequest: {
    type: Function as PropType<
      ApiRequest<SearchParamsByPage> | ApiRequest<any>
    >,
    required: true,
  },
  /**
   * 请求完成钩子，这里可以对返回的数据做进一步处理
   */
  afterListRequest: {
    type: Function as PropType<
      (
        dataList: Ref<any> | Ref<any[]>
      ) => Awaitable<void>
    >,
    required: false,
  },
  /**
   * 每页默认条数
   */
  pageSize: {
    type: Number as PropType<15 | 30 | 50 | 100>,
    default: 15,
  },
  /**
   * 是否分页，默认分页
   */
  usingPaging: {
    type: Boolean,
    default: true,
  },
});

/**
 * 表格列，双向绑定用于调整列显隐
 */
const cols = defineModel("cols", {
  type: Array as PropType<ICol[]>,
  required: true,
});

/**
 * 表格工具栏配置项
 */
const toolsOptions = defineModel("toolsOptions", {
  type: Object as PropType<IPageToolsOptions>,
  required: true,
});

/**
 * 行按钮配置项
 */
const rowBtnsOptions = defineModel("rowBtnsOptions", {
  type: Object as PropType<IPageRowBtnsOptions>,
  default: () => ({} as IPageRowBtnsOptions),
});

// 搜索表单插槽配置项
const searchSlotOpts = computed(
  () =>
    toolsOptions.value.searchFormOptions?.filter(
      (opt: { slot?: string }) => opt.slot
    ) || []
);

// 表单列插槽配置项
const tableSlotCols = computed(() =>
  cols.value.filter(
    (col: { slot?: string; hide?: boolean }) => !col.hide && col.slot
  )
);
// 工具栏新增弹窗表单插槽
const toolsAddDialogSlotOptions = computed(
  () =>
    toolsOptions.value.addFormOptions?.filter(
      (opt: { slot?: string }) => opt.slot
    ) || []
);
// 行按钮编辑弹窗表单插槽
const rowBtnsEditDialogSlotOptions = computed(
  () =>
    rowBtnsOptions.value.editFormOptions?.filter(
      (opt: { slot?: string }) => opt.slot
    ) || []
);
// 行按钮新增弹窗表单插槽
const rowBtnsAddDialogSlotOptions = computed(
  () =>
    rowBtnsOptions.value.addFormOptions?.filter(
      (opt: { slot?: string }) => opt.slot
    ) || []
);
// 行按钮详情弹窗插槽
const rowBtnsDetailDialogSlotOptions = computed(
  () =>
    rowBtnsOptions.value.detailOptions?.filter(
      (opt: { slot?: string }) => opt.slot
    ) || []
);

// loading
const loading = ref(true);
// 列表数据
const tableData = ref([]);
// 当前页
const currPage = ref(1);
// 总条数
const total = ref(0);
// 每页条数
const pageSize = ref<number>(props.pageSize);

// 监听并处理默认值
watch(
  () => toolsOptions.value,
  (newVal) => {
    // 如果未传searchFormParams会导致表单项无法初始化（双向绑定）
    // 所以这里判断如果未传则赋值空对象
    if (newVal.searchFormParams == void 0) {
      toolsOptions.value.searchFormParams = {};
    }
  },
  { immediate: true }
);

// 请求列表
const getList = (function func() {
  loading.value = true;
  props
    .listRequest({
      ...(props.usingPaging
        ? {
            pageNo: currPage.value,
            pageSize: pageSize.value,
          }
        : {}),
      ...(toolsOptions.value.searchFormParams || {}),
    })
    .then(async (res) => {
      const { ok, data } = res;
      if (ok && data) {
        // data数据改为响应式
        const resultData = ref(data);
        // 这里在外部处理list数据
        if (props.afterListRequest) {
          await props.afterListRequest(resultData);
        }
        // 分页情况
        tableData.value = props.usingPaging
          ? resultData.value.records
          : resultData.value;
        total.value = props.usingPaging
          ? resultData.value.total
          : resultData.value.length;
      }
    })
    .finally(() => {
      loading.value = false;
    });
  return func;
})();

// 当前页切换
const pageCurrChange = (val: number) => {
  currPage.value = val;
  getList();
};

// 每页条数切换
const pageSizeChange = (val: number) => {
  pageSize.value = val;
  getList();
};

// 搜索
const handleSearch = () => {
  currPage.value = 1;
  getList();
};

// 重置
const handleReset = () => {
  currPage.value = 1;
  getList();
};

// 工具栏删除
const toolsDelete = async (
  selection: FyTableInstance["selection"],
  callbacks: DeleteCallbacks
) => {
  try {
    const { delRequest, getParamsBeforeDelete } = toolsOptions.value;
    if (!delRequest) {
      console.error("缺少多行删除API方法：delRequest！");
      return;
    }
    if (!getParamsBeforeDelete) {
      console.error("缺少删除前钩子函数：getParamsBeforeDelete！");
      return;
    }
    const params = await getParamsBeforeDelete(selection);

    const res = await delRequest(params);
    const { ok, message } = res;
    if (ok) {
      ElMessage.success(message);
      getList();
    }
  } catch (error) {
    console.error(error);
  }
  callbacks.closeLoading();
};

/**
 * 工具栏提交钩子
 */
const toolsAddDialogSubmit = async (
  formData: Record<string, unknown>,
  callbacks: SubmitCallbacks
) => {
  try {
    const { addRequest, beforeAddSubmit, addFormOptions } = toolsOptions.value;

    if (!addRequest) {
      console.error("缺少新增提交API方法：addRequest！");
      return;
    }

    // 若存在手动上传的上传组件，则在表单验证前执行上传
    if (
      addFormOptions?.some(
        (item) =>
          item.component === "upload" && item.props?.autoUpload === false
      )
    ) {
      await callbacks.uploadSubmit();
    }

    // 响应化
    const formDataRef = ref(formData);

    // 新增钩子
    if (beforeAddSubmit) {
      await beforeAddSubmit(formDataRef);
    }

    // 表单验证
    const valid = await callbacks.validate();
    if (!valid) return;

    const res = await addRequest(formDataRef.value);

    const { ok, message } = res;

    if (ok) {
      ElMessage.success(message);

      getList();

      callbacks.closeDialog();
    }
  } catch (error) {
    console.error(error);
  }
  callbacks.closeLoading();
};

/**
 * 行按钮弹窗提交钩子
 */
const rowBtnsSubmit = async (
  formData: Record<string, unknown>,
  callbacks: SubmitCallbacks,
  btn: "edit" | "add"
) => {
  try {
    const {
      beforeEditSubmit,
      beforeAddSubmit,
      editRequest,
      addRequest,
      editFormOptions,
      addFormOptions,
    } = rowBtnsOptions.value;

    if (btn === "edit" && !editRequest) {
      console.error("缺少编辑提交API方法：editRequest！");
      return;
    }

    if (btn === "add" && !addRequest) {
      console.error("缺少新增提交API方法：addRequest！");
      return;
    }

    // 若存在手动上传的上传组件，则在表单验证前执行上传
    if (
      (btn === "add" ? addFormOptions : editFormOptions)?.some(
        (item) =>
          item.component === "upload" && item.props?.autoUpload === false
      )
    ) {
      await callbacks.uploadSubmit();
    }

    // 响应化
    const formDataRef = ref(formData);

    // 编辑钩子
    if (btn === "edit" && beforeEditSubmit) {
      await beforeEditSubmit(formDataRef);
    }
    // 新增钩子
    if (btn === "add" && beforeAddSubmit) {
      await beforeAddSubmit(formDataRef);
    }

    // 表单验证
    const valid = await callbacks.validate();
    if (!valid) return;

    const res = await (btn === "edit"
      ? editRequest!(formDataRef.value)
      : addRequest!(formDataRef.value));

    const { ok, message } = res;

    if (ok) {
      ElMessage.success(message);

      // 刷新
      if (btn === "edit") {
        getList();
      }
      if (btn === "add") {
        handleReset();
      }

      callbacks.closeDialog();
    }
  } catch (error) {
    console.error(error);
  }
  callbacks.closeLoading();
};

/**
 * 删除按钮事件
 */
const rowBtnsDelete = async (
  rowData: Record<string, unknown>,
  callbacks: DeleteCallbacks
) => {
  try {
    const { delRequest, getParamsBeforeDelete } = rowBtnsOptions.value;

    if (!delRequest) {
      console.error("缺少删除API方法：delRequest！");
      return;
    }

    // 删除按钮默认传参id
    const params = getParamsBeforeDelete
      ? await getParamsBeforeDelete(rowData)
      : rowData.id;

    const res = await delRequest(params);
    const { ok, message } = res;
    if (ok) {
      ElMessage.success(message);
      getList();
    }
  } catch (error) {
    console.error(error);
  }

  callbacks.closeLoading();
};

defineExpose({
  getList,
  handleSearch,
  handleReset,
});
</script>
